头文件
对同一个对象、函数、类等的所有声明的类型都必须一致。所以,递交给编译器的源代码以及后来被连接的东西也必须一致。要达到在不同编译单位中声明的一致性,有一种不完美但却比较简单的方法,那就是将包含界面信息的头文件通过#include包含到可执行代码和/或数据定义的源程序文件里。
#include机制是一种正文操作的概念,用于将源程序片段收集到一起,形成一个提供给编译的单位(文件)。指令
#include "to_be_included"
将用文件to_be_included的内容取代这个#include所在的那一行。该文件的内容应该是C++源代码,因为编译器接着要去读它。
如果要包含标准库文件,那么就应该使用尖括号 <
和 >
而不是引号。例如,
#include <iostream> // 来自标准库包含目录
#include "myheader.h" // 来自当前目录
不幸的是,在一个包含指令中,<>
或 ""
内部的空格也是有意义的:
#include < iostream > // 将无法找到<iostream>
当一个文件被包含到某处之后,每次都需要重新去编译它,这看起来是过分奢侈了。不过,在典型情况下被包含的文件里只有声明,没有需要编译器深入分析的代码。进一步说,许多现在C++实现都提供了对头文件的某种预编译形式,以尽可能减少反复编译同一个头文件所需要的工作。
作为一种经验法则,头文件里可以包括
命名名字空间 | namespace N { /* ... */ } |
类型定义 | struct Point { int x, y; } |
模板声明 | template<class T> class Z; |
模板定义 | template<class T> class V { /* ... */ }; |
函数声明 | extern int strlen(const char*); |
在线函数定义 | inline char get(char* p) { return *p++; } |
数据声明 | extern int a; |
常量定义 | const float pi = 3.141593; |
枚举 | enum Light { red, yellow, green }; |
名字声明 | class Matrix; |
包含指令 | #include <algorithm> |
宏定义 | #define VERSION12 |
条件编译指令 | #ifdef __cplusplus |
注释 | /* check for end of file */ |
这种有关什么可以放入头文件的经验法则并不是语言所要求的。它只是使用#include机制来表示程序的物理结构的一种合理方式。在另一方面,头文件里绝不应该有:
常规的函数定义 | char get(char* p) { return *p++; } |
数据定义 | int a; |
聚集量(aggregate)定义 | short tb1[] = { 1, 2, 3 }; |
无名名字空间 | namespace { /* ... */ } |
导出的(exported)模板定义 | export template<class T> f(T t) { /* ... */ } |
按照习惯,头文件采用后缀 .h
,而包含函数或数据定义的文件用 .c
后缀。它们也因此被分别称为“.h文件”和“.c文件”。其他约定,如.C、.cxx、.cpp和.cc等也常常可以看到。你的编译器手册在这个方面可以有一些特殊东西。
上面建议只在头文件放简单常量的定义,而不放聚集量的定义,其原因是,具体实现很难避免聚集量在几个编译单位中的重复出现。进一步说,那些简单情况都是最常见的东西,因此对于生成好的代码也更重要一些。
对于#include的使用不要过于自作聪明。我的建议是,只用#include包含完整的声明和定义,而且只在全局作用域中,或在连接描述块里,或在转换老代码时在名字空间定义里这样做(9.2.2节)。与别处一样,在这里也要避免玩弄宏魔术。我最不愿意做的事情之一就是:追踪由某个名字引起的一个错误,该名字被宏替换成另一个完全不同的东西,而有关的宏定义又是出现在某个我从来也没听说过的,通过#include间接包含进来的头文件里。
🔚